深坑之Webview,解决H5调用android相机拍照和录像

您所在的位置:网站首页 capture one支持视频拍摄吗 深坑之Webview,解决H5调用android相机拍照和录像

深坑之Webview,解决H5调用android相机拍照和录像

2024-06-15 18:15| 来源: 网络整理| 查看: 265

最近在开发过程中遇到一个问题,主要是调用第三方的实名认证,需要拍照和录像;

办过支付宝大宝卡和腾讯的大王卡的都知道这玩意,办卡的时候就需要进行实名认证,人脸识别;

本来第三方平台(xxx流量公司)说的是直接用WebView加载这个H5界面就完事了,我心想这么简单,那不是分分钟的事,放着后面做(公司就我一个安卓,所以开发都是我说的算_,独立开发有的时候还是挺爽);

结果到项目快要上线的时候,只想说一句mmp,根本调不了相机,这个时候怎么搞,只有自己去实现了,才发现自己已经进了webview的深坑;

到处找资料,发现webview根本不能让h5自己调用,ios是可以的,项目经理就说是我的锅,真特么又一句mmp(关键是这个H5还特么不能改,不能提供给我调用的方法);

进入正题,首先来了解webview,这里我分享两篇大佬的博客 1,WebView开车指南 2,WebView详解 看完这两篇基本你已经可以随意操作webview了, 但是,当你调用相机的时候你才发现这只是入坑的开始

第一个坑

调用相机之后,文件回调不了,其实根本就是没有回调.这个时候你要去检查你的webview的Settings了关键代码,是否给足了权限;

WebSettings settings = webView.getSettings(); settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true); settings.setDomStorageEnabled(true); settings.setDefaultTextEncodingName("UTF-8"); settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true settings.setAllowFileAccess(true); // 是否可访问本地文件,默认值 true // 是否允许通过file url加载的Javascript读取本地文件,默认值 false settings.setAllowFileAccessFromFileURLs(false); // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false settings.setAllowUniversalAccessFromFileURLs(false); //开启JavaScript支持 settings.setJavaScriptEnabled(true); // 支持缩放 settings.setSupportZoom(true);

其中settings.setDomStorageEnabled(true);这个方法当时我没加,血崩了一次;

第二个坑

WebChromeClient的openFileChooser()只调用了一次

首先了解为什么这个方法只调用了一次,看这篇博客就可 Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明 看了他基本你就了解怎么回事了,

简单来说就是每次调用onShowFileChooser或openFileChooser都要设置一个回调;不管你是不是选择了照片,如果没有就回传一个null

第三个坑 怎么区分是要调用相机是需要拍照还是录视频 1,这个时候你就要看WebViewClient的 shouldOverrideUrlLoading()方法了,我是通过拦截url来判断url里面是拍照还是录制视频来加一个flag,然后这样你调用你的相机的时候就可以分别处理了 2,另外如果实在拿不到这个flag,可以通过html的input标签来accept类型判断 在这里插入图片描述

在哪里拿呢

// 5.0+ acceptType public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { String[] acceptTypes = fileChooserParams.getAcceptTypes(); } // For Android 3.0+acceptType public void openFileChooser(ValueCallback uploadMsg, String acceptType) { }

这样就可以拿到是accept进行调用了

第四个坑 android 7.0的FileProvider的坑 看洪阳大佬的这篇博客基本就了解了 Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

最后附上我自己的代码吧

package com.sihaiwanlian.cmccnev.feature.verify.activitys; import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipData; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.SystemClock; import android.provider.MediaStore; import android.support.annotation.RequiresApi; import android.support.v4.content.FileProvider; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.orhanobut.logger.Logger; import com.sihaiwanlian.cmccnev.R; import com.sihaiwanlian.cmccnev.utils.PhotoUtils; import java.io.File; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; public class Certifica extends AppCompatActivity { private final static String TAG = "villa"; @BindView(R.id.titleBar_iv_back) ImageView mTitleBarIvBack; @BindView(R.id.titleBar_btn_back) Button mTitleBarBtnBack; @BindView(R.id.titleBar_centerTV) TextView mTitleBarCenterTV; private WebView webView; private ValueCallback mUploadMessage; private ValueCallback mUploadCallbackAboveL; private final static int PHOTO_REQUEST = 100; private final static int VIDEO_REQUEST = 120; private final static String url = "your_url"; private boolean videoFlag = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_certifica); ButterKnife.bind(this); initToolBar(); initWebView(); } private void initToolBar() { mTitleBarCenterTV.setText("实名认证"); } //初始化webView private void initWebView() { //从布局文件中扩展webView webView = (WebView) this.findViewById(R.id.certifi_webview); initWebViewSetting(); } //初始化webViewSetting @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) private void initWebViewSetting() { WebSettings settings = webView.getSettings(); settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true); settings.setDomStorageEnabled(true); settings.setDefaultTextEncodingName("UTF-8"); settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true settings.setAllowFileAccess(true); // 是否可访问本地文件,默认值 true // 是否允许通过file url加载的Javascript读取本地文件,默认值 false settings.setAllowFileAccessFromFileURLs(false); // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false settings.setAllowUniversalAccessFromFileURLs(false); //开启JavaScript支持 settings.setJavaScriptEnabled(true); // 支持缩放 settings.setSupportZoom(true); //辅助WebView设置处理关于页面跳转,页面请求等操作 webView.setWebViewClient(new MyWebViewClient()); //辅助WebView处理图片上传操作 webView.setWebChromeClient(new MyChromeWebClient()); //加载地址 webView.loadUrl(url); } @OnClick(R.id.titleBar_btn_back) public void onViewClicked() { if (webView.canGoBack()) { webView.goBack();// 返回前一个页面 } else { finish(); } } //自定义 WebViewClient 辅助WebView设置处理关于页面跳转,页面请求等操作【处理tel协议和视频通讯请求url的拦截转发】 private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Logger.e(url); if (!TextUtils.isEmpty(url)) { videoFlag = url.contains("vedio"); } if (url.trim().startsWith("tel")) {//特殊情况tel,调用系统的拨号软件拨号【1111111111】 Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(url)); startActivity(i); } else { String port = url.substring(url.lastIndexOf(":") + 1, url.lastIndexOf("/"));//尝试要拦截的视频通讯url格式(808端口):【http://xxxx:808/?roomName】 if (port.equals("808")) {//特殊情况【若打开的链接是视频通讯地址格式则调用系统浏览器打开】 Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(url)); startActivity(i); } else {//其它非特殊情况全部放行 view.loadUrl(url); } } return true; } } private Uri imageUri; //自定义 WebChromeClient 辅助WebView处理图片上传操作【 文件上传标签】 public class MyChromeWebClient extends WebChromeClient { // For Android 3.0- public void openFileChooser(ValueCallback uploadMsg) { Log.d(TAG, "openFileChoose(ValueCallback uploadMsg)"); mUploadMessage = uploadMsg; if (videoFlag) { recordVideo(); } else { takePhoto(); } } // For Android 3.0+ public void openFileChooser(ValueCallback uploadMsg, String acceptType) { Log.d(TAG, "openFileChoose( ValueCallback uploadMsg, String acceptType )"); mUploadMessage = uploadMsg; if (videoFlag) { recordVideo(); } else { takePhoto(); } } //For Android 4.1 public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { Log.d(TAG, "openFileChoose(ValueCallback uploadMsg, String acceptType, String capture)"); mUploadMessage = uploadMsg; if (videoFlag) { recordVideo(); } else { takePhoto(); } } // For Android 5.0+ public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { Log.d(TAG, "onShowFileChooser(ValueCallback uploadMsg, String acceptType, String capture)"); mUploadCallbackAboveL = filePathCallback; if (videoFlag) { recordVideo(); } else { takePhoto(); } return true; } } /** * 拍照 */ private void takePhoto() { File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg"); imageUri = Uri.fromFile(fileUri); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { imageUri = FileProvider.getUriForFile(Certifica.this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri } PhotoUtils.takePicture(Certifica.this, imageUri, PHOTO_REQUEST); } /** * 录像 */ private void recordVideo() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); //限制时长 intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10); //开启摄像机 startActivityForResult(intent, VIDEO_REQUEST); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //如果按下的是回退键且历史记录里确实还有页面 if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { webView.goBack(); return true; } return super.onKeyDown(keyCode, event); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PHOTO_REQUEST) { if (null == mUploadMessage && null == mUploadCallbackAboveL) return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); if (mUploadCallbackAboveL != null) { onActivityResultAboveL(requestCode, resultCode, data); } else if (mUploadMessage != null) { mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } else if (requestCode == VIDEO_REQUEST) { if (null == mUploadMessage && null == mUploadCallbackAboveL) return; Uri result = data == null || resultCode != RESULT_OK ? null : data.getData(); if (mUploadCallbackAboveL != null) { if (resultCode == RESULT_OK) { mUploadCallbackAboveL.onReceiveValue(new Uri[]{result}); mUploadCallbackAboveL = null; } else { mUploadCallbackAboveL.onReceiveValue(new Uri[]{}); mUploadCallbackAboveL = null; } } else if (mUploadMessage != null) { if (resultCode == RESULT_OK) { mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else { mUploadMessage.onReceiveValue(Uri.EMPTY); mUploadMessage = null; } } } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) { if (requestCode != PHOTO_REQUEST || mUploadCallbackAboveL == null) { return; } Uri[] results = null; if (resultCode == Activity.RESULT_OK) { if (data == null) { results = new Uri[]{imageUri}; } else { String dataString = data.getDataString(); ClipData clipData = data.getClipData(); if (clipData != null) { results = new Uri[clipData.getItemCount()]; for (int i = 0; i < clipData.getItemCount(); i++) { ClipData.Item item = clipData.getItemAt(i); results[i] = item.getUri(); } } if (dataString != null) results = new Uri[]{Uri.parse(dataString)}; } } mUploadCallbackAboveL.onReceiveValue(results); mUploadCallbackAboveL = null; } @Override protected void onDestroy() { super.onDestroy(); if (webView != null) { webView.setWebViewClient(null); webView.setWebChromeClient(null); webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory(); // ((ViewGroup) webView.getParent()).removeView(webView); webView.destroy(); webView = null; } } }

这里面有个photoUtils,已经封装好了各种调用,简单好用

public class PhotoUtils { private static final String TAG = "PhotoUtils"; /** * @param activity 当前activity * @param imageUri 拍照后照片存储路径 * @param requestCode 调用系统相机请求码 */ public static void takePicture(Activity activity, Uri imageUri, int requestCode) { //调用系统相机 Intent intentCamera = new Intent(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件 } intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE); //将拍照结果保存至photo_file的Uri中,不保留在相册中 intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); if (activity!=null){ activity.startActivityForResult(intentCamera, requestCode); } } /** * @param activity 当前activity * @param requestCode 打开相册的请求码 */ public static void openPic(Activity activity, int requestCode) { Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT); photoPickerIntent.setType("image/*"); activity.startActivityForResult(photoPickerIntent, requestCode); } /** * @param activity 当前activity * @param orgUri 剪裁原图的Uri * @param desUri 剪裁后的图片的Uri * @param aspectX X方向的比例 * @param aspectY Y方向的比例 * @param width 剪裁图片的宽度 * @param height 剪裁图片高度 * @param requestCode 剪裁图片的请求码 */ public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) { Intent intent = new Intent("com.android.camera.action.CROP"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } intent.setDataAndType(orgUri, "image/*"); intent.putExtra("crop", "true"); intent.putExtra("aspectX", aspectX); intent.putExtra("aspectY", aspectY); intent.putExtra("outputX", width); intent.putExtra("outputY", height); intent.putExtra("scale", true); //将剪切的图片保存到目标Uri中 intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri); intent.putExtra("return-data", false); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); activity.startActivityForResult(intent, requestCode); } /** * 读取uri所在的图片 * * @param uri 图片对应的Uri * @param mContext 上下文对象 * @return 获取图像的Bitmap */ public static Bitmap getBitmapFromUri(Uri uri, Context mContext) { try { // Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri); Bitmap bitmapFormUri = getBitmapFormUri(mContext, uri); return bitmapFormUri; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 通过uri获取图片并进行压缩 * * @param uri */ public static Bitmap getBitmapFormUri(Context ac, Uri uri) throws FileNotFoundException, IOException { InputStream input = ac.getContentResolver().openInputStream(uri); BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); onlyBoundsOptions.inJustDecodeBounds = true; onlyBoundsOptions.inDither = true;//optional onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional BitmapFactory.decodeStream(input, null, onlyBoundsOptions); input.close(); int originalWidth = onlyBoundsOptions.outWidth; int originalHeight = onlyBoundsOptions.outHeight; if ((originalWidth == -1) || (originalHeight == -1)){ return null; } //图片分辨率以480x800为标准 float hh = 800f;//这里设置高度为800f float ww = 480f;//这里设置宽度为480f //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 int be = 1;//be=1表示不缩放 if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放 be = (int) (originalWidth / ww); } else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放 be = (int) (originalHeight / hh); } if (be 100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩 baos.reset();//重置baos即清空baos //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流 image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中 options -= 10;//每次都减少10 } ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中 Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片 return bitmap; } /** * @param context 上下文对象 * @param uri 当前相册照片的Uri * @return 解析后的Uri对应的String */ @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; String pathHead = "file:///"; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1]; } } // DownloadsProvider else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return pathHead + getDataColumn(context, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[]{split[1]}; return pathHead + getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { return pathHead + getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return pathHead + uri.getPath(); } return null; } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. */ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null){ cursor.close(); } } return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } }

欢迎大家扫描关注作者公众号,长期推送Android技术干货,感觉大家支持:

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3